;(function($) {
	var CFG = {};

	var CACHE = {
		'pages': {}, // caches page html after dom/html-fix, index: url
		'trees': {}, // index: doc_id
		'hists': [], // history, element: url
		'flags': {}, // various flags, index: flag key
	};

	var $doxpane = $('<div/>').addClass('dox-doxpane');
	var $headbar = $('<div/>').addClass('dox-headbar');
	var $wrapper = $('<div/>').addClass('dox-wrapper');
	var $footbar = $('<div/>').addClass('dox-footbar').html(
		'Powered by <a href="http://code.google.com/p/javadox/">Javadox</a>.')
	var $sidebar = $('<div/>').addClass('dox-sidebar');
	var $findsym = $('<div/>').addClass('dox-findsym');
	var $doctree = $('<div/>').addClass('dox-doctree');
	var $midpane = $('<div/>').addClass('dox-midpane');
	var $navibar = $('<div/>').addClass('dox-navibar');
	var $histbox = $('<div/>').addClass('dox-histbox');
	var $docpane = $('<div/>').addClass('dox-docpane');

	var histmgr = (function() {
		var hists = []; // element: url
		var currp = -1; // index of current position in history
		                // (-1 < [].length) so initially can_next() returns false
		return {
			'can_prev': function() {
				return (currp > 0);
			},
			'prev': function() {
				if (histmgr.can_prev()) {
					--currp;
//					alert("histmgr: prev: (" + currp + "/" + hists.length + ") " + hists[currp]);
					return hists[currp];
				}
				return null;
			},
			'can_next': function() {
				return (currp < (hists.length - 1));
			},
			'next': function() {
				if (histmgr.can_next()) {
					++currp;
//					alert("histmgr: next: (" + currp + "/" + hists.length + ") " + hists[currp]);
					return hists[currp];
				}
				return null;
			},
			'push': function(url) {
//				alert('histmgr: push: ' + url);
				if (histmgr.can_next()) {
					hists.splice(currp + 1, hists.length - currp - 1);
				}
				hists.push(url);
				++currp;
//				alert("histmgr: push: (" + currp + "/" + hists.length + ") " + url);
			},
		};
	})();

	var load_page = function(url) {
		if (CACHE.pages[url]) {
			$docpane.html(CACHE.pages[url]);
		} else {
			var is_doc = false;
			$.each(CACHE.trees, function(doc_id) {
				if (CACHE.trees[doc_id].base_url &&
				    (0 == url.indexOf(CACHE.trees[doc_id].base_url))) {
					is_doc = true;
				}
			});
//			console.debug('is_doc: ' + is_doc + ', url=' + url);
			$.get(url, {}, function(data, textStatus) {
				if (is_doc) {
					data = fix_html(data);
				} else {
					var p = data.indexOf('<body');
					if (p >= 0) {
						p = data.indexOf('>', p);
						data = data.substr(p + 1);
					}
					data = data.substr(0, data.indexOf('</body'));
				}
				var container = $('<div/>').html(data);
				if (is_doc) {
					fix_dom(container, url);
				}
				CACHE.pages[url] = container.html();
				$docpane.html(CACHE.pages[url]);
			});
		}
	};

	$.fn.javadox = function(docs, opts) {
		if (!opts) {
			opts = {
				'startpage': 'javadox.html',
			};
		}

		// Setup "live" handlers at the very beginning.
		$('.dox-doctree a').live('click', function(e) {
			e.preventDefault();
			var url = $(this).attr('href');
			if (url) {
				histmgr.push(url);
				load_page(url);
			}
			return false;
		});

		$.each(docs, function(doc_id, doc_url) {
			if (!CACHE.trees[doc_id]) {
				CACHE.trees[doc_id] = {};
			}
			CACHE.trees[doc_id].base_url = doc_url;
		});

		// Setup layout
		$(this)
		.append(
			$doxpane
			.append($headbar.html('<span class="dox-progress-cur">0</span>'))
			.append(
				$wrapper
				.append(
					$sidebar
					.append($findsym)
					.append($doctree)
				)
				.append(
					$midpane
					.append($navibar)
					.append($docpane)
				)
			)
			.append($footbar)
		);

		histmgr.push(opts.startpage);
		load_page(opts.startpage);

		$findsym.append($('<input/>').attr('type', 'search').attr('placeholder', '{findsym}'));
		$navibar
		.append(
			$('<input/>').attr('type', 'button').val('Previous') // &#x2397;
			.bind('click', function() {
				load_page(histmgr.prev());
			})
		)
		.append(
			$('<input/>').attr('type', 'button').val('Next') // &#x2398;
			.bind('click', function() {
				load_page(histmgr.next());
			})
		).append(
			$histbox
			.append(
				$('<select/>')
				.append($('<option/>').val(opts.startpage).text('Javadox'))
			)
		);

		var doctree_ul = $('<ul/>').appendTo($doctree);
		doctree_ul.csstree();

		$.each(docs, function(doc_id, doc_url) {
			assert(CACHE.trees[doc_id]);

			var doc_name = $.trim(doc_id);
			var doc_name_css = 'dox-doc-' + doc_name.replace(/\./g, '_');
			var doc_li = $('<li/>').append(
				$('<a/>').text(doc_name)
				.addClass(doc_name_css)
				.attr('href', doc_url + '/overview-summary.html')
			).append(
				$(' <a href="' + doc_url + '/overview-summary.html" target="_blank">&#x239a;</a>')
			).appendTo(doctree_ul);
			doc_li.csstree_add();
			var doc_ul = $('<ul/>').hide().appendTo(doc_li);

			// Load packages in this doc.
			$.get(doc_url + '/allclasses-frame.html', {}, function(data, textStatus) {
				var ab = 0;
				while ((ab = data.indexOf('<A HREF="', ab)) >= 0) {
					ab += 9;
					var ae = data.indexOf('"', ab);
					if (ae >= 0) {
						var href = data.substr(ab, ae - ab);
						var spec = href.substr(0, href.indexOf('.html')).split('/');
						var name = spec.join('.');
						var clss = spec.pop();     // class name
						var pack = spec.join('.'); // package name
						if ((clss.length > 0) && (pack.length > 0)) {
							assert(CACHE.trees[doc_id]);
							if (!CACHE.trees[doc_id].packages) {
								CACHE.trees[doc_id].packages = {};
							}
							if (!CACHE.trees[doc_id].packages[pack]) {
								CACHE.trees[doc_id].packages[pack] = {};
							}
							if (!CACHE.trees[doc_id].packages[pack].classes) {
								CACHE.trees[doc_id].packages[pack].classes = {};
							}
							CACHE.trees[doc_id].packages[pack].classes[clss] = {};
						}
					}
				} // while (ab)
				$.each(sort(keys(CACHE.trees[doc_id].packages)), function(index, pkg_name) {
					var pkg_css = doc_name_css + '-pkg-' + pkg_name.replace(/\./g, '-');
					var pkg_url = doc_url + '/' + pkg_name.split(/\./).join('/') + '/package-summary.html';
					var pkg_li = $('<li/>').append(
						$('<a/>').text(pkg_name)
						.addClass(pkg_css)
						.attr('href', pkg_url)
					).appendTo(doc_ul);
					pkg_li.csstree_add();
					$('.dox-progress-cur').text(parseInt($('.dox-progress-cur').text()) + 1);

					var pkg_ul = $('<ul/>').hide().appendTo(pkg_li);
					$.each(sort(keys(CACHE.trees[doc_id].packages[pkg_name].classes)), function(index, cls_name) {
						var cls_css = pkg_css + '-cls-' + cls_name.replace(/\./g, '-');
						var cls_url = doc_url + '/' + pkg_name.split(/\./).join('/') + '/' + cls_name + '.html';
						var cls_li = $('<li/>').append(
							$('<a/>').text(cls_name)
							.addClass(cls_css)
							.attr('href', cls_url)
						).appendTo(pkg_ul);
						cls_li.csstree_add();
						$('.dox-progress-cur').text(parseInt($('.dox-progress-cur').text()) + 1);
					});
				});
			});
		});

	}; // $.fn.javadox

	var fix_html = function(html) {
		var p = html.indexOf('<!-- =========== END OF NAVBAR =========== -->');
		if (p < 0) {
			p = html.indexOf('<!-- ========= END OF TOP NAVBAR ========= -->');
		}
		html = html.substr(p);
		p = html.indexOf('<!-- ========== START OF NAVBAR ========== -->');
		if (p < 0) {
			p = html.indexOf('<!-- ======= START OF BOTTOM NAVBAR ====== -->');
		}
		html = html.substr(0, p);
		p = html.lastIndexOf('<HR>');
		if (p >= 0) {
			html = html.substr(0, p);
		}
		p = html.indexOf('<HR>');
		if (p >= 0) {
			html = html.substr(p + 4);
		}
		html = html.replace(/ CELLPADDING="[^"]*"/g, '');
		html = html.replace(/ CELLSPACING="[^"]*"/g, '');
		html = html.replace(/ WIDTH="[^"]*"/g, '');
		html = html.replace(/ BGCOLOR="[^"]*"/g, '');
//		html = html.replace(/<\/?B>/g, '');
		html = html.replace(/<\/?FONT[^>]*>/g, '');
		html = html.replace(/<\s*div\s+style="[^"]*"\s*>/ig, '<div>');
		return html;
	}

	var fix_url = function(url, base) {
		if (url.match(/^(http|https):\/\//)) {
			return url;
		}
		else if (url.match(/^#/)) {
			return base.replace(/#.*$/, '') + url;
		}
		else {
			return base.replace(/\?.*$/, '').replace(/\/[^\/]*$/, '/') + url;
		}
	};

	var fix_dom = function(dom, url_base) {
		var $dom = $(dom);
		// We load javadoc via ajax, so the base url would be incorrect if the
		// href attribute is relative path.
		$dom.find('a[href]:not([href^="http://"]):not([href^="https://"])').each(function() {
			// Fix url base for relative paths.
			var $a = $(this);
			$a.attr('href', fix_url($a.attr('href'), url_base));
		});
		$dom.find('a[href^="http://"],a[href^="https://"]').each(function() {
			// Force open in new window
			var $a = $(this);
			$a.attr('target', '_blank');
		});
		$dom.find('img[src]:not([src^="http://"]):not([src^="https://"])').each(function() {
			var $img = $(this);
			$img.attr('src', url_base + $img.attr('src'));
		});
		$dom.find('center > h1').each(function() {
			var $center = $(this).addClass('dox-javadoc-title').parent('center');
			$center.replaceWith($center.contents());
		});
		$dom.find('blockquote > pre').each(function() {
			var $blockquote = $(this).parent('blockquote');
			$blockquote.replaceWith($blockquote.contents());
		});
		// Tag <xmp> is deprecated and is no longer supported in HTML5.
		$dom.find('xmp').each(function() {
			$(this).replaceWith($('<pre/>').append($(this).contents()));
		});
		$dom.find('pre:contains("org.apache.hadoop.conf.Configuration")').each(function() {
		});
	};

	var num_props = function(hash) {
		var count = 0;
		if (hash.__count__ != undefined) {
			// Firefox provides a magic __count__ property
			return hash.__count__;
		}
		for (k in hash) {
			if (hash.hasOwnProperty(k)) {
				++count;
			}
		}
		return count;
	};

	var keys = function(hash) {
		var ks = [];
		for (k in hash) {
			if (hash.hasOwnProperty(k)) {
				ks.push(k);
			}
		}
		return ks;
	};

	var sort = function(list) {
		var copy = list.slice(0); // shallow copy (clone)
		copy.sort();
		return copy;
	};

	var assert = function(expr, msg) {
		if (!(expr)) {
			console.error('ASSERT ERROR: ' + msg);
		}
	};

})(jQuery);
